要素研究
▼ 始めに ここではシューティングゲームに必要な各要素の研究を行います。具体的には ライブラリの使い方の習得やハードウェアに関連する概念の理解を目的とします。 ▼ 画面モードの設定 画面モードは DOS コール、IOCS コール、ハードウェア直接アクセスの3種類 があります。これらのうちどれを使うのが適切なのでしょうか? これを判断す るために簡単にこれらのコールについて解説します。 DOS コールはアセンブラでは $FFxx (xx は機能)として、Cでは(LIBC で は)_dos_xxxx() のように記述される関数です。C言語の場合は libdos.a の中 にある _dos_xxxx() から、アセンブラの場合は DOS _XXXX から HUMAN.SYS の 中の該当ルーチンを呼び出します。 例)_dos_print("test¥n"); IOCS コールはアセンブラでは TRAP #15、Cでは(LIBC では)_iocs_xxxx() のように記述される関数です。C言語の場合は libiocs.a の中にある _iocs_xxxx() から、アセンブラの場合は IOCS _XXXX から IOCS ROM(もしくは IOCS.X/HIOCS.X)の中の該当ルーチンを呼び出します。 例)_iocs_b_print ("test¥n"); ハードウェア直接アクセスはそれらを介さず、直接アドレスを指定して値を読 み書きします。 例)*(unsigned char *) 0xe00000 = 0x10; これらのコールは一見無関係なように見えますが、その内部では密接に関係し あっています。_dos_print() はその内部で _iocs_b_print() を呼びだし、 _iocs_b_print() はその内部でハードウェアに直接アクセスしてテキスト画面に 文字を書いています。DOS コールの場合サブルーチンがサブルーチンを呼んでい ることから動作速度が遅く、ハードウェア直接アクセスが一番高速です。X680x0 ではプログラミングモデルとして、このような「サブルーチンがサブルーチンを 呼んで…」という階層的な構造を採用しています。 ではこれらを念頭に入れた上で機能面について検討してみます。今回のシュー ティングゲームでは 256x256ドット 31kHz グラフィック画面 256 色 ドット正 方形モードを使うのですが(今決めた)、 ・DOS コールでは 256x256ドットモードに設定できない ・IOCS コールでは 256x256ドットモードに設定できるが正方形モード に設定できない ・ハードウェア直接アクセスならば全てのモードを設定できる という事実があります。 ここまで読むと「全てハードウェア直接アクセスが良い?」という結論に達し てしまいそうですが、「下の階層で勝手に行った事は上の階層では知らない」と いう現実があります。例えば DOS レベルで 512x512 ドットモードにした場合、 DOS は画面モードを変えたことを「知って」いますし、下請けで呼ばれた IOCS も変えたことを「知って」います。しかしいきなり IOCS コールで画面モードを 変えた場合どうなるでしょう? この場合 DOS は画面モードを変えたことを 「知らない」ので画面モードは 512x512 ドットモードに変わってしまったにも 関わらず、相変わらず 768x512 ドットモードだと思い込んで画面の横幅が96桁 とみなして文字を出力してしまいます(その結果文字の右端が画面外に出てしま い見えないという不都合が発生する)。いきなりハードウェア直接アクセスをし た場合は、DOS も IOCS も「知らない」ので同様の不都合は IOCS レベルでも起 きてしまいます。 機能面を考えると全てを満足させる完璧な方法は、実は、ありません。一番正 しい回答は「256x256 ドット正方形モードは使わない」なのですがそれも困りま す。では現実的にはどうしたら良いのでしょう? 今回選択した方法は、 ・DOS コールは見捨てる ・極力 IOCS コールで設定 ・どうしても機能的にダメな時、速度が要求される場合はハードウェア 直接アクセス です。 今回のようにどうしても反則技を使わなければならない場合は、それがどのよ うな影響を及ぼすかを察知した上でうまく使っていくしかありません。 ではサンプルプログラムを見ていきましょう。 ● サンプル実行=非対応メニューです ◎ ソースリスト このサンプルではまず画面モードを _iocs_crtmod() で変え、次にハードウェ ア直接アクセスで正方形モードに変えています。 _iocs_crtmod() は HRL ビッ ト(0xe80006 のビット1)を変えてくれないので、画面モードを戻す時には自 前で戻しています。HRL ビットを立てたままだと一部のプログラムが誤動作する ので(スプライトエディタ SM.X 等)、忘れずに戻して下さい。 ▼ ジョイスティック入力 シューティングゲームであるからにはジョイスティックを入力デバイスとして 使用します。ではジョイスティックの状態はどのように調べるのでしょうか。 そこでライブラリマニュアルを眺めると…ありました。_iocs_joyget() 関数 です。これを使ってジョイスティックの入力を行ってみます。 ● サンプル実行(要ジョイスティック)=非対応メニューです ◎ ソースリスト _iocs_joyget() 関数は IOCS コールの関数ですので、 ---- JOYTEST.C ---- #include <sys/iocs.h> /* _iocs_XXX を使用するために */ ------------------- の1行が必要です。また、_iocs_joyget() 関数は libiocs.a に収録されてい るため、libiocs.a をリンクする必要があります。 ---- MAKEFILE ---- LIBS = libc.a libgnu.a libiocs.a (中略) joytest.x: joytest.o $(LD) $^ -o $@ -l $(LIBS) ------------------ _iocs_joyget() 関数で注意しなければならないのは「押された時に0になる」 という事です。この事に注意すれば特に問題はないでしょう。 ジョイスティックの状態を調べるにはハードウェアのポートを直接調べるとい う方法もあります。しかし、 ・ジョイスティックは頻繁に調べるものではない(1垂直同期ごとに1 回;後述)ので IOCS コールでも速度的な問題はない ・機能的に不満はない という事から IOCS コールを使用しています。付け加えるならば、 ・IOCS コールをフックしているジョイスティックドライバがあるため、 なるべく IOCS コールを使いたい(ハードウェアを直接調べるとこれ らのドライバが無効になってしまう) という事もあります。 ▼ スプライトの表示 今回はスプライトはXSPを使うことにしたので、マニュアルをよく読みます。 するとサンプルプログラムが入っていたので、それを見ながらプログラムを組ん でみましょう。 ◎ XSPのマニュアル 使用するスプライトは以下の通りです。 ◎ 敵弾 ● サンプル実行(要ジョイスティック)=非対応メニューです ◎ ソースリスト このサンプルではエラーチェックを全く行っていないので注意して下さい。本 番では必ずエラーチェックするように。 ▼ 複合スプライトの表示 XSPでは通常のスプライト(16x16ドット)の表示だけでなくスプライトを 複数集めて巨大化した「複合スプライト」という形式が扱えることがわかります。 そこで複合スプライトを使って32x32ドットの大きさの自機を表示させてみます。 まず必要なのはスプライトデータ。これは「てぺ」で作成します。 ◎ てぺの画面 もちろん SM.X でも構いませんがてぺの方が高機能なので筆者はこちらの方を 愛用しています。いきなり編集を始められる SM.X と違って始めはとっつきにく い部分もありますが、一度使い方を覚えてしまえば非常に強力なツールです。こ の画面の .TPE ファイルも収録してありますので T2PE.X PLAYER.TPE [RETURN] と起動すると作業状況が再現されます。 自機のスプライトを元に .OBJ ファイルを作成します。.OBJ ファイルは複合 スプライトの相対座標を指示するテキストファイルです。 ◎ 自機 自機の中心を(0,0)として4枚のスプライトを、 (-16,-16),(0,-16),(-16,0), (0,0)に配置して大きな複合スプライトを作ります。 ◎ PLAYER.OBJ ここで「スプライトパターンがもったいない」と気付いた方はスルドイ。自機 は左右対象なのでスプライトの左右反転機能を使えばスプライトパターンは2枚 で済みます。しかしXSPは更にスルドイので、左右反転で重なるパターンの場 合、自動的に左右反転するようなファイルを出力してくれます。賢い。そしてこ の .OBJ ファイルを CVOBJ.X で変換すると .XSP/.FRM/.REF の3つのファイル が生成されます。 作成したファイルを読み込んで表示してみます。ワークの確保やポインタの補 正…という細かい部分はサンプルプログラムを参照してみて下さい。 ● サンプル実行=非対応メニューです ◎ ソースリスト では複数の種類の複数スプライトを複数表示してみます。.OBJ ファイルを作 成して xobj_set() を表示したい複合スプライト数だけです。ここでちょっとし たテクニック。てぺにはテキストエディタが装備されています。基本的な機能の みの簡易版ですが、それでも非常に役に立ちます。.OBJ ファイルを作るには、 「スプライト0番を(-16,-16)に、1番を(0,-16)に、えーっと(-16,0)に表示す るのは何番だっけ?」となり、普通ならテキストエディタとスプライトエディタ を行ったりきたりする事になるのですが、てぺであればスプライトエディタ上で .OBJ ファイルの編集が可能です。いや便利。 ◎ スプライトを見ながら .OBJ ファイルを編集 で、作成した PLAYER.OBJ。 ◎ PLAYER.OBJ 以下の行に注目して下さい。 --- PLAYER.OBJ ---- XY_OFFSET = 0 -2 * 座標のオフセット ------------------- これはスプライトの全体の座標を(0,-2)ずらす指定です。つまり座標 (-16,-16)は(-16,-18)になります。もちろん素直に(-16,-18)と書いてもいいの ですが、.OBJ ファイルを一通り作ったあと、「複合スプライトの原点を下に2 ドットずらした方が当たり判定が自然になるんじゃないかな?」と考え、.OBJ ファイルを書き直す事を考えたのですが、全ての行を変更する必要があり面倒だっ たため XY_OFFSET で回避しました。 ● サンプル実行=非対応メニューです ◎ ソースリスト ▼ 垂直同期という概念 テレビやパソコンのモニターは動画を表示することができます。というのは半 分嘘で、厳密には静止画を1秒間に何十枚も高速に表示することにより疑似的な 動画表示を実現しています。テレビは1秒間60枚、X680x0 の 31KHz モードは1 秒間に 55枚(厳密には55.46枚)の画面を表示しています。この1秒間 55枚と いう数字は X68000-10MHz機から X68030+060turbo まで変わりません。そのため この数値はゲームの時間管理をする上で非常に有効に使えます。 ここでモニター=CRT()の原理の説明をします。CRTは電子銃の入った 真空管です。 CRT表面 ┌ │ 人はこちらから │ 電子 電子銃 見る → │←──────◇ │ │ └ なんとも簡略化されていますがCRTだと思って下さい。CRTの中には電子 銃があり、そこから電子が発射されます。CRT表面には蛍光物質が塗られてい て、電子が当たると光ります。その光を人が見ます。 さて、これだけでは1点が光るだけです。そこで電子に磁場を掛けます。電磁 石です。磁場を掛けるとフレミング左手の法則により電子の軌道が変わります。 CRT表面 CRT表面 ┌ ┌ │\電子 │ 磁場 │ \ ↑ 電子銃 │ ↓ 電子銃 │ \───◇ │ /───◇ │ ↑ │ / ↓ │ 磁場 │/電子 └ └ 磁場を画面上左から右へ 磁場を画面上右から左へ 掛けた場合、電子は上向 掛けた場合、電子は下向 きに曲がる きに曲がる 最初「左から右向き」の磁場を掛けておき、それを徐々に「右から左向き」の 磁場へと変えていくと電子の軌道は画面の上から下へ変化します。CRT表面に 当たった電子は光ることから、このように磁場を変化させると画面上に光った縦 線が引かれます。 CRT画面(前から見た図) ┌───────┐ │ │光 │ │ │ │ │ │ │ │ │ │ │ ↓ │ └───────┘ 同様に磁場を上から下へ掛けた場合電子は左に、下から上へ掛けた場合電子は 右に曲がります。 CRT画面(前から見た図) ┌───────┐ │ │ │ 光 │ │──────→│ │ │ │ │ └───────┘ この2つを組み合わせて画面全てを光らせてみます。C言語風に書くと、 while (1) { 磁場X方向 = 右向き 磁場Y方向 = 下向き do { do { 磁場X方向 += 少し左向き } while ( 光が画面右端に達するまで ); 磁場Y方向 += 少し上向き } while ( 光が画面下端に達するまで ); } 光は画面上を以下のように動くでしょう。 CRT画面(前から見た図) ┌───────┐ (1)│──────→│(2へ) (2)│──────→│(3へ) (3)│──────→│(4へ) (4)│──────→│(5へ) (5)│──────→│(1へ) └───────┘ このように光は画面左上から右下へと順に移動します。画面上の光を移動させ る事を「走査」と呼び、この横線1本を「走査線(ラスター)」と呼びます。 ラスターを走らせるに当たって必要なのが「水平同期信号」と「垂直同期信号」 です。CRTは水平同期信号を受け取った瞬間、磁場を変化させ光を左端へ移動 させます。同様に垂直同期信号を受け取った瞬間、光を上端へ移動させます。要 するに「磁場を初期値にリセットする信号」ですね。 垂直同期信号は 55.46Hz = 1秒間に 55.46回、水平同期信号は 31.5KHz = 1 秒間に 31500回発生します。この 55.46回というのが1秒間に表示する画面の枚 数、31500回というのが1秒間に走るラスターの本数です。1画面当たりのラス ターの本数は 31500/55.46 = 568本です。768x512 ドットモードの場合ラスター は 512本じゃないの?という疑問を持たれる方も多いと思いますが、ラスターは Y方向のドット数分ギリギリではなく、ある程度余裕を持って走らせています。 ラスター 568本のうち、上40本と下16本は未使用です。 これらの数値は CRTC レジスタの中に出てきます。 ・CRTC R04($e80008) : 垂直トータル -1 ラスターの本数 -1 = 568-1 = 567 を設定 ・CRTC R06($e8000c) : 垂直表示開始位置 -1 表示を開始するラスター -1 = 41-1 = 40 を設定 ・CRTC R07($e8000e) : 垂直表示終了位置 -1 表示を終了するラスター -1 = 41+512-1 = 552 を設定 さて、この光の移動と共に電子銃の出力を変化させます。電子銃の出力が0の 場合、電子は出ませんから画面は光りません。出力を最大にした場合、画面は最 大輝度で光ります。移動と出力、この2つを同時に動かすと画面上に任意の絵を 描くことができます。これがモノクロCRTの原理です。カラーの場合はRGB 3本分の電子銃で同様の事を行います。 ここまで詳しく解説してきましたが、今回必要なのは「1秒間に55回垂直同期 信号が発生する」ということだけです。これだけ押さえた上で以下次項。 ▼ 時間管理の必要性 X68000 は初代機から X68030+060turbo まで様々な機種があります。これらは 基本的に互換性が保たれており(そうでない部分もちょっとだけあるけど)、基 本的には全ての機種で同じプログラムが走ります。素晴らしいことです。 機種によって違う点は動作速度です。新しいマシンほど処理が速く、同じプロ グラムでも短い実行時間で処理を終えることができます。ですが、シューティン グゲームではこの事が大変な問題になります。 以下の図を御覧下さい。遅いマシンと速いマシンでの処理速度の違いです。 ・遅いマシンでの処理 ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ ──┤ 処理 ├┤ 処理 ├┤ 処理 ├┤ 処理 ├┤ 処理 ├┤ 処理 ├ └────┘└────┘└────┘└────┘└────┘└────┘ ・速いマシンでの処理 ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ ──┤処理├┤処理├┤処理├┤処理├┤処理├┤処理├┤処理├┤処理├┤処理├ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘ 遅いマシンで6回の処理を終える間に速いマシンでは9回の処理を終える事が できました。めでたしめでたし。いやよくない。この処理内容を「スプライトを 1ドット動かす」処理だと思って下さい。遅いマシンは6ドット動く時間で速い マシンでは9ドットも動いてしまいます。つまり「速いマシンほど自機や敵弾が 速く動く」事になります…ゲームになりませんね。X680x0 界でも 10MHz 機しか なかった時代に作られたプログラムではこのような事がありました。しかし今と なってはこのようなプログラムは許されません。 そこで登場したのが「垂直同期を用いた時間管理」です。 ▼ 垂直同期を用いた時間管理 前述のように画面は上から下へ&左から右へ描かれていきます。CRTが一番 右下の表示を終えた瞬間、「垂直同期信号」が発生します。垂直同期信号はアド レス $e88001 の bit4 で見る事ができます。bit4 が 0 になった時が垂直同期 信号です。 ・垂直同期信号 ──┐┌──────────┐┌──────────┐┌────────── ││ ││ ││ └┘ └┘ └┘ │←──- 1/55 秒 ──→│ 垂直同期信号を使って以下のようなアルゴリズムで処理を行います。 「垂直同期信号を検出したら処理を開始する。処理が終了しても次の垂 直同期信号を検出するまでは処理を行わない(待つ)」 図示すると以下のようになります。 ・垂直同期信号 ──┐┌──────────┐┌──────────┐┌────────── ││ ││ ││ └┘ └┘ └┘ │←──- 1/55 秒 ──→│ ・遅いマシンでの処理 ┌────┐ ┌────┐ ┌────┐ ──┤ 処理 ├──────┤ 処理 ├──────┤ 処理 ├────── └────┘ └────┘ └────┘ ・早いマシンでの処理 ┌──┐ ┌──┐ ┌──┐ ──┤処理├────────┤処理├────────┤処理├──────── └──┘ └──┘ └──┘ これで遅いマシンでも速いマシンでも同じ速度でプログラムを実行する事がで きるようになりました。シューティングゲームではこのような時間管理法を採用 しています。次の垂直同期信号を待つためには $e88001 の bit4 を監視するの ですが、XSPではまさにこの用途向けの xsp_vsynx() という関数があるので 今回はこちらを使います。また、今回は使いませんが、垂直同期が発生したら割 り込みルーチンを実行することができます。それが IOCS _VDISP です。 ところで、処理を行っていない時間はどうしているのでしょう? 答:ひたす ら待ちます。無限ループですね。MPUパワーがもったいない!と言う声が上が りそうですが、そういうものです。余った時間に何か別の処理を行うという事も できますが、プログラムが複雑になるので今回は行いません。 ・余談:マルチタスクOSでは処理が終わった瞬間、別のタスクに処理 を移すことによりMPUパワーを有効に使えます。例えばシューティ ングゲームとコンパイラが同時に走っている場合、「シューティング ゲームの処理が終わったら余った時間でコンパイル」する事ができま す。…が、現実にはそんなに甘くありません。「垂直同期を検出した らシューティングゲームに処理を移す」方法がないのです。コンパイ ラは垂直同期を無視して動き続け、コンパイルがある程度進んだらシュー ティングゲームに処理を移します。「どの程度コンパイルしたら」か はOSのみぞ知る問題です。困りますね。 この問題は「垂直同期を検出したらシューティングゲームに処理を 移す」OSがあれば解決します。このように割り込みでタスク切り替 えを行えるOSの事を「リアルタイムOS」と呼びます。ちなみに、 Windows も MacOS も Linux も BSD も Human68K もリアルタイムO Sではありません。零式OSはリアルタイムOSです。(Linux には RT-Linux というリアルタイム改造版があるようです) ▼ 処理落ちの理論・xsp_vsync(0) シューティングゲームをプレイしていると、敵や敵弾が大量に出現するところ で速度が落ちる事があります。俗に言う「処理落ち」ですね(スローが掛かる、 ウエイトが入る、などとも言います)。処理落ちの仕方を見ていると、敵や敵弾 の数に比例して速度が落ちるのではなく、数がある一定数を越えた時点で「がく ん」と速度が落ちるのに気付かれるでしょう。これは先ほどの図で説明が付きま す。 ・垂直同期信号 ──┐┌──────┐┌──────┐┌──────┐┌──────┐┌────── ││ ││ ││ ││ ││ └┘ └┘ └┘ └┘ └┘ │←- 1/55 秒 →│ ・処理落ち ┌─────────┐ ┌─────────┐ ┌─────── ──┤ 処理 ├─────┤ 処理 ├─────┤ 処理 └─────────┘ └─────────┘ └─────── ・正常な処理 ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ──┤ 処理 ├──┤ 処理 ├──┤ 処理 ├──┤ 処理 ├──┤ 処理 ├── └────┘ └────┘ └────┘ └────┘ └────┘ 処理落ちは処理が 1/55 秒以内に起こらなかった時に発生します。「垂直同期 を検出したら次の処理を開始する」というアルゴリズムでは処理中に垂直同期が 発生して場合、「次の垂直同期を待って」しまいます。結果垂直同期2回ごとに 処理が行われるようになり、正常な処理の時と比較して処理速度が半分になりま す。これが「がくん」と速度が落ちるからくりです。 さて、XSPではこれより賢い垂直同期検出機構を備えています。それが先ほ どの xsp_vsync() 関数です。これは「垂直同期を待つ関数。但し垂直同期を取 り逃した場合待たずにすぐ返る」という関数です。この機能を使うためには引き 数に 0 を指定します。これを図示すると以下のようになります。 ・垂直同期信号 ──┐┌──────┐┌──────┐┌──────┐┌──────┐┌────── ││ ││ ││ ││ ││ └┘ └┘ └┘ └┘ └┘ │←- 1/55 秒 →│ ・xsp_vsync(0) 時の処理落ち ┌─────────┐┌─────────┐┌─────────┐┌────── ──┤ 処理 ├┤ 処理 ├┤ 処理 ├┤ 処理 └─────────┘└─────────┘└─────────┘└────── 確かに処理落ちは起こるのですが先ほどと比較して「少し遅くなった」程度で 済むことがわかります。賢いですね。是非使いましょう。 ▼ 処理落ちの理論・高クロック機と低クロック機の場合 処理落ちは理論上は全ての X680x0 でも起こります。高クロック機と低クロッ ク機の違いは「どの程度処理が増えたら 1/55秒を越えるか」にかかってきます。 例えば 10MHz 機では敵弾 120発で 1/55秒を越えるのに対し、X68030 では 500 発の時に 1/55秒を越える、という事が考えられます。 これはマシンによって処理落ちの「敷居値」が違うという事です。これを解決 する方法は今のところありません。敵弾の数を数え、高クロック機ではその分だ け無駄な処理を行う、という方法もあるのですが機種によって命令の実行クロッ ク数が違ってくるので機種分だけ場合分けが必要になります。この件に関しては 今後の検討課題としておきます。 消極的な解決策としては「低クロック機でも処理落ちが落ちない程度のするに する」という方法があります。前述の例だと敵弾は120発以上出さない、等。こ のような選択肢も場合によっては有効です。 ▼ 処理落ちの理論・アルゴリズムの選択 コンピューターのアルゴリズムは以下の4種類に分けられます。 1)常に高速なアルゴリズム 2)常に低速なアルゴリズム 3)対象の数が少ない時に高速なアルゴリズム 4)対象の数が多い時に高速なアルゴリズム 1)があれば文句無く採用するのですが、現実的には3)か4)であることが ほとんどです。ではシューティングゲームにおいてはどちらを採用すべきかを検 討します。 「対象の数」を「敵弾数」に置き換えて考えます。 A)敵弾が10発の時は高速で200発の時は低速なアルゴリズム B)敵弾が10発の時は低速で200発の時は高速なアルゴリズム 前述のようにシューティングゲームでは「垂直同期を越えるかどうか」が判断 基準となります。ですから敵弾数が少ない時は比較する必要がありません。垂直 同期を越えないのであればいくら処理が速くでも垂直同期待ちで時間をつぶすだ けですから。むしろ比較すべきは敵弾数が多い時です。この時は垂直同期を越え るかどうか、というギリギリのラインですから、この時にこそ高速なアルゴリズ ムを選択すべきです。結論としてはB)つまり4)を採用します。 ▼ 関数へのポインタ ありがちな処理として、「変数 i の値によって違う関数を実行する」という ものがあります。シューティングゲームでも敵キャラの種類に応じて違う関数を 呼び出したりするような処理があります。 このような分岐を実現する方法としては、if 文を並べる方法も勿論あります が、一般的には switch 文を使います。 例)i によって分岐 switch (i) { case 0: EnemyMove0(); /* 敵0の移動関数を呼ぶ */ break; case 1: EnemyMove1(); /* 敵1の移動関数を呼ぶ */ break; : (以下略) が、種類が多くなってくると switch でも面倒です。そこで今回は関数へのポ インタを使ってスマートに解決します。以下の例では入力した数値に従って違う ルーチンに分岐します。 ● JUMPTBL.X を実行=非対応メニューです ◎ JUMPTBL.C ▼ 音関係の処理 音関係の処理にはゲームでの使用も考慮された高速・高機能な音源ドライバ ZMUSIC を使用します。音楽はFM8音で再生し、効果音はPCMで鳴らします。 PCM付きの音楽で効果音はFM音源という方法もあるのですが、今回は(も) 簡単な前者の方法を使用します。今後の研究課題ですね。 ZMUSIC の使い方自体は ZMUSIC のマニュアルに掲載されているのでそのまま アセンブラから呼び出せる形の zmcall.s を作成します。使用例については以下 の zmdplay.x をご覧下さい。 ● ZMDPLAY.X を実行=非対応メニューです ◎ ZMCALL.S ◎ ZMDPLAY.C (EOF)